就讓我們進入寫扣環節。
為了先能有一個可以跑的程式,我做出以下限制:
Server 會提供一個 endpoint,然後會回一個結構如下的 JSON:
{
"comps": [{
"type": "text || button,之後會隨著 component 增加而增加",
"id": "這個 component 的唯一 id",
# text type 會有的屬性
"text": "type 為 text 時顯示的文字",
# button type 會有的屬性
"label": "type 為 button 時在 button 上的文字",
}, {
# ...更多 text/button component
}]
}
目前來說 Container 的功能就只是提供第一層的 Component List,他本身甚至不會當 Component。
type Container struct {
Type string `json:"type"`
ID string `json:"id"`
// 目前只有這有作用
Comp []any `json:"comp"`
}
func NewContainer(id string) *Container {
return &Container{
Type: "container",
ID: id,
}
}
Text Component 就只是一行文字,我們也先不給其他屬性,只需要 Text。
type textComp struct {
Type string `json:"type"`
ID string `json:"id"`
Text string `json:"text"`
}
func newText(text string) *textComp {
return &textComp{
Type: "text",
ID: text,
Text: text,
}
}
func Text(c *Container, text string) {
c.Comp = append(c.Comp, newText(text))
}
Button Component 目前並沒有互動功能,只是在按鈕上顯示 label,所以 Button 函數直接先 return false。我們晚點把 State 加進來。
type buttonComp struct {
Type string `json:"type"`
ID string `json:"id"`
Label string `json:"label"`
}
func newButton(label string) *buttonComp {
return &buttonComp{
Type: "button",
ID: label,
Label: label,
}
}
func Button(c *Container, label string) bool {
c.Comp = append(c.Comp, newButton(label))
// 我們暫時還沒有 State 可以拿,只能先這樣
return false
}
Server 的邏輯。
index.html
<script>
function createButton(comp) {
const newButton = document.createElement('button')
newButton.innerText = comp.label
return newButton
}
function createText(comp) {
const newText = document.createElement('div')
newText.innerText = comp.text
return newText
}
// 只有先在一開始觸發。
fetch('/api/run').then(resp => resp.json()).then(body => {
var rootDiv = document.getElementById("root")
body.comp.forEach(comp => {
if (comp.type === 'button') {
rootDiv.appendChild(createButton(comp))
} else if (comp.type === 'text') {
rootDiv.appendChild(createText(comp))
} else {
console.warn(e.type)
}
});
})
</script>
<div id="root">
</div>
pkg.go
在 API 部分,我們就是跑完 Script 後,把 Container JSON Marshal 起來傳回去。
//go:embed index.html
var indexBody []byte
func Run(f func(*Container)) {
// 取得畫面JSON
http.HandleFunc("/api/run", func(w http.ResponseWriter, r *http.Request) {
rootContainer := NewContainer("root")
f(rootContainer)
bs, err := json.Marshal(rootContainer)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write(bs)
})
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write(indexBody)
})
http.ListenAndServe(":8763", nil)
}
終於,我們踏出了第一步,只是目前連和使用者互動都做不到。
main.go
package main
func main() {
Run(func(root *Container) {
Button(root, "hello")
Text(root, "world")
})
}